﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using SilverTile.Xaml;
using SilverArcade.Graphics;

namespace SilverTile
{
    public class Viewport : Renderable, IViewport
    {

        private int[] _backgroundBuffer;
        private bool _backgroundPrepared = false;
        private WriteableBitmap _bmp;

        public Viewport(int width, int height)
            : base(width, height)
        {
            _activeSprites = new Dictionary<string, ISprite>();
            _backgroundSprites = new List<ISprite>();
            _bmp = new WriteableBitmap(Width, Height);
        }

        public BitmapSource Output
        {
            get { return _bmp; }
        }

        public int[] Pixels { get { return _bmp.Pixels;  } }

        private int _backgroundColor = Colors.Black.ToInt32();
        public Color BackgroundColor
        {
            get { return _backgroundColor.ToColor(); ;}
            set { _backgroundColor = value.ToInt32(); }
        }

        private readonly Dictionary<string, ISprite> _activeSprites;
        public IDictionary<string, ISprite> ActiveSprites
        {
            get { return _activeSprites; }
        }

        private List<ISprite> _backgroundSprites;
        public IEnumerable<ISprite> BackgroundSprites
        {
            get { return _backgroundSprites; }
        }

        public void AddSprite(string key, ISprite sprite)
        {
            _activeSprites[key] = sprite;
            sprite.Viewport = this;
        }

        public void AddBackgroundSprite(ISprite sprite, int x, int y)
        {
            _backgroundSprites.Add(sprite);
            sprite.X = x;
            sprite.Y = y;
            sprite.Viewport = this;
        }

        public ISprite RemoveSprite(string key)
        {
            try
            {
                ISprite removed = _activeSprites[key];
                _activeSprites.Remove(key);
                return removed;
            }
            catch (KeyNotFoundException keyEx)
            {
                throw new ArgumentOutOfRangeException("Sprite " + key + " doesn't exist", keyEx);
            }
        }

        public ISprite RemoveSprite(ISprite sprite)
        {
            try
            {
                string spriteKey = _activeSprites.Where(kvp => kvp.Value.Equals(sprite)).Select(kvp => kvp.Key).First();
                return RemoveSprite(spriteKey);
            }
            catch (InvalidOperationException invEx)
            {
                throw new ArgumentOutOfRangeException("The sprite isn't in the viewports active sprites list", invEx);
            }
        }

        public override void Render()
        {

            if(!_backgroundPrepared)
            {
                PrepareBackground();
            }

            // Render background
            Array.Copy(_backgroundBuffer, _bmp.Pixels, _backgroundBuffer.Length);

            foreach (ISprite fgSprite in _activeSprites.Values)
            {
                _bmp.Blit(fgSprite, BlendMode.AlphaBlend);
            }

            _bmp.Invalidate();

        }

        public override void Update(TimeSpan timeSinceLastFrame)
        {
            foreach (ISprite bgSprite in _backgroundSprites)
            {
                bgSprite.Update(timeSinceLastFrame);
            }
            foreach (ISprite fgSprite in _activeSprites.Values)
            {
                fgSprite.Update(timeSinceLastFrame);
            }
        }
        
        public void RenderTileRowOverwrite(int x, int y, int xfrom, int xto, int row, int tileWidth, int[] pixels)
        {

            int startOffsetBytes = row * tileWidth * 4 + xfrom * 4;
            int destOffsetBytes = (y + row) * Width * 4 + (x + xfrom) * 4;
            int rowWidthBytes = (xto - xfrom) * 4;

            Buffer.BlockCopy(pixels, startOffsetBytes, _bmp.Pixels, destOffsetBytes, rowWidthBytes);
        }

        private void PrepareBackground()
        {

            // Set all pixels to bg color
            _backgroundBuffer = new int[Width * Height];
            for (int p = 0; p < _backgroundBuffer.Length; p++)
            {
                _backgroundBuffer[p] = _backgroundColor;
            }
            
            // Paint the background buffer to the surface
            Array.Copy(_backgroundBuffer, _bmp.Pixels, _bmp.Pixels.Length);
            
            // Render all background tiles
            foreach (ISprite bgSprite in _backgroundSprites)
            {
                _bmp.Blit(bgSprite, BlendMode.None);
            }

            // Update the output surface
            _bmp.Invalidate();

            // Copy the pixels to the background buffer
            Array.Copy(_bmp.Pixels, _backgroundBuffer, _bmp.Pixels.Length);

            _backgroundPrepared = true;

        }

        public void SetGameArea(GameArea gameArea)
        {

            _backgroundSprites = new List<ISprite>();

            foreach (SpriteTile tile in gameArea.BackgroundSprites)
            {

                ISprite sprite = new Sprite(tile.Frame.Frame)
                {
                    Blocking = tile.Blocking
                };

                AddBackgroundSprite(sprite, tile.X, tile.Y);
       
            }
            
        }

        public IEnumerable<ISprite> GetForegroundSpriteCollisions(ISprite sprite)
        {
            return GetForegroundSpriteCollisions(sprite, 0);
        }

        public IEnumerable<ISprite> GetForegroundSpriteCollisions(ISprite sprite, int expandBy)
        {

            var collidesWith =
                ActiveSprites
                .Values
                .Where(s => s != sprite && s.CollidesWith(sprite, s.X, s.Y, expandBy)).ToList();

            return collidesWith;

        }

        public Collision SpriteWillCollideWithBackground(ISprite sprite, int x, int y, int expandBy)
        {
            var collides =
                BackgroundSprites
                .Where(b => b.Blocking)
                .Select(b => sprite.CollidesWithDir(b, x, y, expandBy));


            if (collides.Count() == 0)
            {
                return Collision.None;
            }

            Collision collision = Collision.None;
            foreach (Collision coll in collides)
            {
                collision = collision.Set(coll);
            }
            if (collision != Collision.None)
            {
                collision = collision.Clear(Collision.None);
            }
            return collision;

        }

    }
}
